﻿using System;
using System.IO;

namespace MJTop.NGif
{
    public class LZWEncoder
    {
        private int a_count;

        private byte[] accum = new byte[256];

        private readonly static int BITS;

        private bool clear_flg;

        private int ClearCode;

        private int[] codetab = new int[LZWEncoder.HSIZE];

        private int cur_accum;

        private int cur_bits;

        private int curPixel;

        private readonly static int EOF;

        private int EOFCode;

        private int free_ent;

        private int g_init_bits;

        private int hsize = LZWEncoder.HSIZE;

        private readonly static int HSIZE;

        private int[] htab = new int[LZWEncoder.HSIZE];

        private int imgH;

        private int imgW;

        private int initCodeSize;

        private int[] masks = new int[] { 0, 1, 3, 7, 15, 31, 63, 127, 255, 511, 1023, 2047, 4095, 8191, 16383, 32767, 65535 };

        private int maxbits = LZWEncoder.BITS;

        private int maxcode;

        private int maxmaxcode = 1 << LZWEncoder.BITS & 31;

        private int n_bits;

        private byte[] pixAry;

        private int remaining;

        static LZWEncoder()
        {
            LZWEncoder.BITS = 12;
            LZWEncoder.EOF = -1;
            LZWEncoder.HSIZE = 5003;
        }

        public LZWEncoder(int width, int height, byte[] pixels, int color_depth)
        {
            this.imgW = width;
            this.imgH = height;
            this.pixAry = pixels;
            this.initCodeSize = Math.Max(2, color_depth);
        }

        private void Add(byte c, Stream outs)
        {
            byte[] numArray = this.accum;
            int aCount = this.a_count;
            this.a_count = aCount + 1;
            numArray[aCount] = c;
            if (this.a_count >= 254)
            {
                this.Flush(outs);
            }
        }

        private void ClearTable(Stream outs)
        {
            this.ResetCodeTable(this.hsize);
            this.free_ent = this.ClearCode + 2;
            this.clear_flg = true;
            this.Output(this.ClearCode, outs);
        }

        private void Compress(int init_bits, Stream outs)
        {
            int i;
            this.g_init_bits = init_bits;
            this.clear_flg = false;
            this.n_bits = this.g_init_bits;
            this.maxcode = this.MaxCode(this.n_bits);
            this.ClearCode = 1 << (init_bits - 1 & 31);
            this.EOFCode = this.ClearCode + 1;
            this.free_ent = this.ClearCode + 2;
            this.a_count = 0;
            int num = this.NextPixel();
            int num1 = 0;
            for (i = this.hsize; i < 65536; i *= 2)
            {
                num1++;
            }
            num1 = 8 - num1;
            int num2 = this.hsize;
            this.ResetCodeTable(num2);
            this.Output(this.ClearCode, outs);
            while (true)
            {
                Label0:
                int num3 = this.NextPixel();
                int num4 = num3;
                if (num3 == LZWEncoder.EOF)
                {
                    break;
                }
                i = (num4 << (this.maxbits & 31)) + num;
                int num5 = num4 << (num1 & 31) ^ num;
                if (this.htab[num5] != i)
                {
                    if (this.htab[num5] >= 0)
                    {
                        int num6 = num2 - num5;
                        if (num5 == 0)
                        {
                            num6 = 1;
                        }
                        do
                        {
                            num5 -= num6;
                            if (num5 < 0)
                            {
                                num5 += num2;
                            }
                            if (this.htab[num5] != i)
                            {
                                continue;
                            }
                            num = this.codetab[num5];
                            goto Label0;
                        }
                        while (this.htab[num5] >= 0);
                    }
                    this.Output(num, outs);
                    num = num4;
                    if (this.free_ent >= this.maxmaxcode)
                    {
                        this.ClearTable(outs);
                    }
                    else
                    {
                        int[] numArray = this.codetab;
                        int freeEnt = this.free_ent;
                        this.free_ent = freeEnt + 1;
                        numArray[num5] = freeEnt;
                        this.htab[num5] = i;
                    }
                }
                else
                {
                    num = this.codetab[num5];
                }
            }
            this.Output(num, outs);
            this.Output(this.EOFCode, outs);
        }

        public void Encode(Stream os)
        {
            os.WriteByte(Convert.ToByte(this.initCodeSize));
            this.remaining = this.imgW * this.imgH;
            this.curPixel = 0;
            this.Compress(this.initCodeSize + 1, os);
            os.WriteByte(0);
        }

        private void Flush(Stream outs)
        {
            if (this.a_count > 0)
            {
                outs.WriteByte(Convert.ToByte(this.a_count));
                outs.Write(this.accum, 0, this.a_count);
                this.a_count = 0;
            }
        }

        private int MaxCode(int n_bits)
        {
            return (1 << (n_bits & 31)) - 1;
        }

        private int NextPixel()
        {
            int eOF;
            if (this.remaining != 0)
            {
                this.remaining--;
                if (this.curPixel + 1 >= this.pixAry.GetUpperBound(0))
                {
                    eOF = 255;
                }
                else
                {
                    byte[] numArray = this.pixAry;
                    int num = this.curPixel;
                    this.curPixel = num + 1;
                    eOF = numArray[num] & 255;
                }
            }
            else
            {
                eOF = LZWEncoder.EOF;
            }
            return eOF;
        }

        private void Output(int code, Stream outs)
        {
            this.cur_accum &= this.masks[this.cur_bits];
            if (this.cur_bits <= 0)
            {
                this.cur_accum = code;
            }
            else
            {
                this.cur_accum = this.cur_accum | code << (this.cur_bits & 31);
            }
            this.cur_bits += this.n_bits;
            while (this.cur_bits >= 8)
            {
                this.Add((byte)(this.cur_accum & 255), outs);
                this.cur_accum >>= 8;
                this.cur_bits -= 8;
            }
            if ((this.free_ent > this.maxcode ? true : this.clear_flg))
            {
                if (!this.clear_flg)
                {
                    this.n_bits++;
                    if (this.n_bits != this.maxbits)
                    {
                        this.maxcode = this.MaxCode(this.n_bits);
                    }
                    else
                    {
                        this.maxcode = this.maxmaxcode;
                    }
                }
                else
                {
                    int gInitBits = this.g_init_bits;
                    int num = gInitBits;
                    this.n_bits = gInitBits;
                    this.maxcode = this.MaxCode(num);
                    this.clear_flg = false;
                }
            }
            if (code == this.EOFCode)
            {
                while (this.cur_bits > 0)
                {
                    this.Add((byte)(this.cur_accum & 255), outs);
                    this.cur_accum >>= 8;
                    this.cur_bits -= 8;
                }
                this.Flush(outs);
            }
        }

        private void ResetCodeTable(int hsize)
        {
            for (int i = 0; i < hsize; i++)
            {
                this.htab[i] = -1;
            }
        }
    }
}